/* jstile.layer.js
 * 
 * The MIT License
 * 
 * Copyright (c) 2009 Christiaan Baartse <christiaan@baartse.nl>
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
(function(){
	var window = this;
	if(!window.jstile) window.jstile = {};
	var jstile = window.jstile;

	/**
	 * Layer Class
	 * @param Int		width			The width of the layer in pixels
	 * @param Int		height			The height of the layer in pixels
	 * @param Int		framerate		FPS
	 * @param String	background		Background color used. False to not clear the layer, true for a transparent layer.
	 * @param Function	onBeforeRefresh	This function gets executed before each refresh. If it returns false the refresh is aborted.
	 * @param Bool		doubleBuffering	Use double buffering
	 */
	jstile.Layer = function(width, height, framerate, background, onBeforeRefresh, doubleBuffering)
	{
		this.width = width;
		this.height = height;
		this.framerate = framerate;
		this.background = background;
		this.onBeforeRefresh = onBeforeRefresh;
		this.doubleBuffering = !!doubleBuffering;
		
		this.layer = false;
		this.context = false;
		this.interval = false;
		this.framerateMs = Math.round(1000 / this.framerate);
		this.objects = {}; // Objects are kept by id here
		this.objectsArr = []; // Objects are sorted by zindex in here
	};

	/**
	 * Initialize the Layer object creating the layer node
	 * @param String|Node	container	The id or the node itself that should contain the layer
	 * @return jstile.Layer Returns this to allow chaining
	 */
	jstile.Layer.prototype.init = function(container)
	{
		if(typeof(container) == 'string')
		{
			container = document.getElementById(container);
		}

		if(!container || typeof(container.innerHTML) != 'string')
		{
			throw 'Container not found';
		}

		this.layer = document.createElement('canvas');
		this.layer.height = this.height;
		this.layer.width = this.width;
		
		if(!this.layer.getContext)
		{
			delete this.layer;
			throw 'Browser does not support the HTML 5 <canvas> tag';
		}

		this.context = this.layer.getContext('2d');
		
		if(this.doubleBuffering) {
			this.output = document.createElement('canvas');
			this.output.height = this.height;
			this.output.width = this.width;
			this.outputContext = this.output.getContext('2d');
			container.appendChild(this.output);
		}
		else {
			container.appendChild(this.layer);
		}
		

		if(this.framerate) {
			var self = this;
			this.interval = setInterval(function(){ self.refresh(); }, this.framerateMs);
		}
		
		return this;
	};

	/**
	 * Redraws the layer
	 */
	jstile.Layer.prototype.refresh = function()
	{
		if(this.onBeforeRefresh && false === this.onBeforeRefresh()) {
			return;
		}
		
		var objects = this.objectsArr, drawn = {};
		
		if(typeof(this.background) == 'string') {
			// set the background color
			var prevFillstyle = this.context.fillStyle; 
			this.context.fillStyle = this.background;
			this.context.fillRect(0, 0, this.width, this.height);
		}
		else if(this.background !== false) {
			// Clean the layer
			this.context.clearRect (0, 0, this.width, this.height);
		}
		
		for (var object, i = 0, len = objects.length; i < len; ++i) {
			object = objects[i];
			if(!object.onDrawCallback || false !== object.onDrawCallback()) {
				object.draw(this);
			}
		}
		
		if(this.doubleBuffering) {
			this.outputContext.clearRect (0, 0, this.width, this.height);
			this.outputContext.drawImage(this.layer, 0, 0, this.width, this.height);
		}
	};

	/**
	 * Sort the registered objects and deletes expired ones
	 * @todo Is this really optimal?
	 * @return jstile.Layer	this
	 */
	jstile.Layer.prototype.prepareObjects = function()
	{
		var id, object, i, z2objects = {}, sortArr = [];

		for (id in this.objects)
		{
			object = this.objects[id];
			
			if(!(z2objects[object.zindex] instanceof Array))
			{
				z2objects[object.zindex] = [];
			}
			
			z2objects[object.zindex].push(object);
		}
		
		for ( var zindex in z2objects) {
			sortArr.push(zindex);
		}

		sortArr.sort(function(a, b) { return a - b; });
		this.objectsArr = [];
		for (i = 0; i < sortArr.length; i++) {
			this.objectsArr = this.objectsArr.concat(z2objects[sortArr[i]]);
		}

		return this;
	};

	/**
	 * Add a object to the layer
	 * @param jstile.Object		object
	 * @param Bool				noprepare If set to true it won't auto reorder the objects
	 * @return jstile.Layer			this
	 */
	jstile.Layer.prototype.addObject = function(object, noprepare)
	{
		if(object instanceof Array) {
			for (var i = 0; i < object.length; i++) {
				this.addObject(object[i], true);
			}
		}
		else {
			object.setLayer(this);
			this.objects[object.id] = object;
			
			for (var id in object.children) {
				this.addObject(object.children[id], true);
			}
		}

		if(!noprepare) {
			this.prepareObjects();
		}
		return this;
	};

	/**
	 * Remove a object from the layer
	 * @param jstile.Object		object
	 * @return jstile.Layer			this
	 */
	jstile.Layer.prototype.removeObject = function(object, noprepare)
	{
		if(object instanceof Array) {
			for (var i = 0; i < object.length; i++) {
				this.removeObject(object[i], true);
			}
		}
		else {
			delete this.objects[object.id];
			object.setLayer(false);
			
			for (var id in object.children) {
				this.addObject(object.children[id], true);
			}
		}
		
		if(!noprepare) {
			this.prepareObjects();
		}
		return this;
	};

})();